#include <mutex>
#include <condition_variable>
#include <functional>
#include <queue>
#include <thread>

template <typename F>
class ThreadPool {
private:
    struct data {
        ThreadPool<F> *pThreadPool;
        std::mutex mtx_;
        std::condition_variable cond_;
        bool is_shutdown_ = false;
        std::queue<F> tasks_;
        int runing_counter = 0;
    };

public:
    explicit ThreadPool(size_t thread_count)
    : data_(std::make_shared<data>()) {
        thread_count_ = thread_count;
        data_->pThreadPool = this;
        for (size_t i = 0; i < thread_count_; ++i) {
            std::thread(ConsumeThread, data_).detach();
        }
    }

    ThreadPool() = default;
    ThreadPool(ThreadPool&&) = default;

    ~ThreadPool() {
        if ((bool) data_) {
            {
                std::lock_guard<std::mutex> lk(data_->mtx_);
                data_->is_shutdown_ = true;
            }
            data_->cond_.notify_all();
        }
    }

    int GetRuningCounter() {
        int counter;
        {
            std::lock_guard<std::mutex> lk(data_->mtx_);
            counter = data_->runing_counter;
        }
        return counter;
    }

    static void ConsumeThread(std::shared_ptr<data> _data) {
        std::unique_lock<std::mutex> lk(_data->mtx_);
        for (;;) {
            if (!_data->tasks_.empty()) {
                auto current = std::move(_data->tasks_.front());
                _data->tasks_.pop();
                _data->runing_counter++;
                lk.unlock();
                current->Run(_data->pThreadPool);
                lk.lock();
                _data->runing_counter--;
            } else if (_data->is_shutdown_) {
                break;
            } else {
                _data->cond_.wait(lk);
            }
        }
    }

    void execute(F task) {

        size_t queue_size;
        for (;;)
        {
            {
                std::lock_guard<std::mutex> lk(data_->mtx_);
                queue_size = data_->tasks_.size();
            }

            if (queue_size > thread_count_)
                sleep(10);
            else
                break;
        }

        {
            std::lock_guard<std::mutex> lk(data_->mtx_);
            data_->tasks_.emplace(std::forward<F>(task));
        }
        data_->cond_.notify_one();
    }

private:
    std::shared_ptr<data> data_;
    size_t thread_count_;
};
